/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.biz.modular.licenseapplication.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import vip.xiaonuo.biz.modular.customerinfo.entity.CustomerInfo;
import vip.xiaonuo.biz.modular.customerinfo.service.CustomerInfoService;
import vip.xiaonuo.biz.modular.licenseapplication.enums.LicenseApplicationEnum;
import vip.xiaonuo.biz.modular.licenseapplication.param.*;
import vip.xiaonuo.biz.modular.softwareversion.entity.SoftwareVersion;
import vip.xiaonuo.biz.modular.softwareversion.service.SoftwareVersionService;
import vip.xiaonuo.common.enums.CommonSortOrderEnum;
import vip.xiaonuo.common.exception.CommonException;
import vip.xiaonuo.common.page.CommonPageRequest;
import vip.xiaonuo.biz.modular.licenseapplication.entity.LicenseApplication;
import vip.xiaonuo.biz.modular.licenseapplication.mapper.LicenseApplicationMapper;
import vip.xiaonuo.biz.modular.licenseapplication.service.LicenseApplicationService;
import vip.xiaonuo.common.prop.CommonProperties;
import vip.xiaonuo.dev.api.DevFileApi;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

/**
 * 授权申请Service接口实现类
 *
 * @author xiaochen
 * @date  2025/06/10 15:31
 **/
@Service
public class LicenseApplicationServiceImpl extends ServiceImpl<LicenseApplicationMapper, LicenseApplication> implements LicenseApplicationService {
    private static ObjectMapper objectMapper = new ObjectMapper();

    public final static String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    @Resource
    CustomerInfoService customerInfoService;

    @Resource
    SoftwareVersionService softwareVersionService;

    @Resource
    private CommonProperties commonProperties;

    @Autowired
    DevFileApi devFileApi;

    @Override
    public Page<LicenseApplication> page(LicenseApplicationPageParam licenseApplicationPageParam) {
        QueryWrapper<LicenseApplication> queryWrapper = new QueryWrapper<LicenseApplication>().checkSqlInjection();
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getApplicantId())) {
            queryWrapper.lambda().eq(LicenseApplication::getApplicantId, licenseApplicationPageParam.getApplicantId());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getCustomerId())) {
            queryWrapper.lambda().eq(LicenseApplication::getCustomerId, licenseApplicationPageParam.getCustomerId());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getDescription())) {
            queryWrapper.lambda().like(LicenseApplication::getDescription, licenseApplicationPageParam.getDescription());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getContractNo())) {
            queryWrapper.lambda().like(LicenseApplication::getContractNo, licenseApplicationPageParam.getContractNo());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getStartStartDate()) && ObjectUtil.isNotEmpty(licenseApplicationPageParam.getEndStartDate())) {
            queryWrapper.lambda().between(LicenseApplication::getStartDate, licenseApplicationPageParam.getStartStartDate(), licenseApplicationPageParam.getEndStartDate());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getStartEndDate()) && ObjectUtil.isNotEmpty(licenseApplicationPageParam.getEndEndDate())) {
            queryWrapper.lambda().between(LicenseApplication::getEndDate, licenseApplicationPageParam.getStartEndDate(), licenseApplicationPageParam.getEndEndDate());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getMacAddress())) {
            queryWrapper.lambda().like(LicenseApplication::getMacAddress, licenseApplicationPageParam.getMacAddress());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getBuildState())) {
            queryWrapper.lambda().eq(LicenseApplication::getBuildState, licenseApplicationPageParam.getBuildState());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getApprovalStatus())) {
            queryWrapper.lambda().eq(LicenseApplication::getApprovalStatus, licenseApplicationPageParam.getApprovalStatus());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getState())) {
            queryWrapper.lambda().eq(LicenseApplication::getState, licenseApplicationPageParam.getState());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getType())) {
            queryWrapper.lambda().eq(LicenseApplication::getType, licenseApplicationPageParam.getType());
        }
        if(ObjectUtil.isNotEmpty(licenseApplicationPageParam.getVersion())) {
            queryWrapper.lambda().eq(LicenseApplication::getVersion, licenseApplicationPageParam.getVersion());
        }
        if(ObjectUtil.isAllNotEmpty(licenseApplicationPageParam.getSortField(), licenseApplicationPageParam.getSortOrder())) {
            CommonSortOrderEnum.validate(licenseApplicationPageParam.getSortOrder());
            queryWrapper.orderBy(true, licenseApplicationPageParam.getSortOrder().equals(CommonSortOrderEnum.ASC.getValue()),
                    StrUtil.toUnderlineCase(licenseApplicationPageParam.getSortField()));
        } else {
            queryWrapper.lambda().orderByDesc(LicenseApplication::getCreateDate);
        }
        Page<LicenseApplication> res = this.page(CommonPageRequest.defaultPage(), queryWrapper);

        String apiUrl = commonProperties.getBackendUrl();
        res.getRecords().forEach(item->{
            if(item.getFileId()!=null){
                item.setDownloadPath(apiUrl+"/dev/file/download?id="+item.getFileId());
            }
        });
        return res;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void add(LicenseApplicationAddParam licenseApplicationAddParam) {
        LicenseApplication licenseApplication = BeanUtil.toBean(licenseApplicationAddParam, LicenseApplication.class);
        licenseApplication.setBuildState(LicenseApplicationEnum.NOT_BUILD.getValue());
        licenseApplication.setApprovalStatus(LicenseApplicationEnum.PENDING.getValue());
        this.save(licenseApplication);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void extend(LicenseApplicationAddParam licenseApplicationAddParam) {
        LicenseApplication oldLicenseApplication = BeanUtil.toBean(licenseApplicationAddParam, LicenseApplication.class);
        LicenseApplication newLicenseApplication = new LicenseApplication();
        BeanUtil.copyProperties(oldLicenseApplication, newLicenseApplication);

        newLicenseApplication.setBuildState(LicenseApplicationEnum.NOT_BUILD.getValue());
        newLicenseApplication.setApprovalStatus(LicenseApplicationEnum.PENDING.getValue());
        newLicenseApplication.setId(null);
        this.save(newLicenseApplication);
        oldLicenseApplication.setState(LicenseApplicationEnum.RETIRE.getValue());
        this.updateById(oldLicenseApplication);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void edit(LicenseApplicationEditParam licenseApplicationEditParam) {
        LicenseApplication licenseApplication = this.queryEntity(licenseApplicationEditParam.getId());
        BeanUtil.copyProperties(licenseApplicationEditParam, licenseApplication);
        this.updateById(licenseApplication);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(List<LicenseApplicationIdParam> licenseApplicationIdParamList) {
        // 执行删除
        this.removeByIds(CollStreamUtil.toList(licenseApplicationIdParamList, LicenseApplicationIdParam::getId));
    }

    @Override
    public LicenseApplication detail(LicenseApplicationIdParam licenseApplicationIdParam) {
        return this.queryEntity(licenseApplicationIdParam.getId());
    }

    @Override
    public LicenseApplication queryEntity(String id) {
        LicenseApplication licenseApplication = this.getById(id);
        if(ObjectUtil.isEmpty(licenseApplication)) {
            throw new CommonException("授权申请不存在，id值为：{}", id);
        }
        return licenseApplication;
    }
    @Override
    public void approve(LicenseApplicationApproveParam licenseApplicationApproveParam) {

        String id = licenseApplicationApproveParam.getId();
        LicenseApplication licenseApplication = this.getById(id);
        if (ObjectUtil.isEmpty(licenseApplication)) {
            throw new CommonException("授权申请不存在，id值为：{}", id);
        }
        licenseApplication.setApprovalStatus(licenseApplicationApproveParam.getResult());
        this.updateById(licenseApplication);
    }

    @Override
    public String create(LicenseApplicationIdParam licenseApplicationIdParam) {

        String id = licenseApplicationIdParam.getId();
        LicenseApplication licenseApplication = this.getById(id);
        if(ObjectUtil.isEmpty(licenseApplication)) {
            throw new CommonException("授权申请不存在，id值为：{}", id);
        }
        String version = licenseApplication.getVersion();
        SoftwareVersion softwareVersion = softwareVersionService.queryEntity(version);
        try {
            modifyConfigFile(softwareVersion,  licenseApplication);
            int r = generalLicenseFile(softwareVersion, licenseApplication);
            if (r == 0){
                licenseApplication.setCreateDate(new Date());
                licenseApplication.setBuildState(LicenseApplicationEnum.BUILT.getValue());
                String s = uploadLicenseFile(softwareVersion);
                licenseApplication.setFileId(s);
                if (softwareVersion.getBuilderType().equals("8.0")){
                    String licenseInfoPath = softwareVersion.getPath()+File.separator+softwareVersion.getLicenseFileName()+".info";
                    String readTxtFileStr = readTxtFile(licenseInfoPath);
                    ObjectNode jsonNodes = (ObjectNode) objectMapper.readTree(readTxtFileStr);
                    if (jsonNodes.has("macAddress")){
                        JsonNode macAddress = jsonNodes.get("macAddress");
                        if (macAddress instanceof ArrayNode){
                            ArrayNode macAddressArrNode = (ArrayNode) macAddress;
                            String macs = "";
                            for (JsonNode jn: macAddressArrNode){
                                String mac = jn.asText();
                                if (mac!=null&&!mac.isEmpty())
                                macs = macs + "," + mac;
                            }
                            if (macs.startsWith(","))
                                macs = macs.substring(1);
                            licenseApplication.setMacAddress(macs);
                        }
                    }
                }
                this.updateById(licenseApplication);
            }
            return ""+r;
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
            return "-1";
        } catch (ParserConfigurationException | SAXException | TransformerException e) {
            e.printStackTrace();
            return "-2";
        }
    }

    private String uploadLicenseFile(SoftwareVersion softwareVersion) throws IOException {
        File licenseFile = new File(softwareVersion.getPath() +File.separator+ softwareVersion.getLicenseFileName());

        FileInputStream inputStream = new FileInputStream(licenseFile);
        MultipartFile multipartFile = new MockMultipartFile(licenseFile.getName(), licenseFile.getName(), "application/octet-stream", inputStream);
        String s = devFileApi.uploadDynamicReturnId(multipartFile);
        return s;
    }

    private static int generalLicenseFile(SoftwareVersion softwareVersion, LicenseApplication licenseApplication) throws IOException, InterruptedException {
        String scriptName = softwareVersion.getScriptName();
        String path = softwareVersion.getPath();
        if (softwareVersion.getBuilderType()!=null&& softwareVersion.getBuilderType().equals("5.0")) {
            Process exec = Runtime.getRuntime().exec(scriptName, null, new File(path));
            int r = exec.waitFor();
            BufferedReader input = new BufferedReader(new InputStreamReader(exec.getInputStream()));
            String line = "";
            while ((line = input.readLine()) != null) {
                System.out.println(line);
                if (StrUtil.isNumeric(line)) {
                    r = Integer.valueOf(line).intValue();
                }
            }
            input.close();
            BufferedReader err = new BufferedReader(new InputStreamReader(exec.getErrorStream()));
            String errLine = "";
            while ((errLine = err.readLine()) != null) {
                System.out.println(errLine);
                r = -1;
            }

            return r;
        }else if (softwareVersion.getBuilderType()!=null&& softwareVersion.getBuilderType().equals("8.0")){
            String[] cmd;
            if (licenseApplication.getMachineInfo()!=null&& !licenseApplication.getMachineInfo().isEmpty()) {
                cmd = new String[7];
            }else {
                cmd = new String[8];
            }
            cmd[0]= "java";
            cmd[1]= "-jar";
            cmd[2]= softwareVersion.getScriptName();
            cmd[3]= softwareVersion.getUserInfoFileName();
            cmd[4]= softwareVersion.getConfigFileName();
            cmd[5]= softwareVersion.getLicenseFileName();
            cmd[6]= softwareVersion.getLicenseFileName()+".info";
            if (licenseApplication.getMachineInfo()!=null&& !licenseApplication.getMachineInfo().isEmpty()) {

            }else {
                cmd[7] = "-txt";
            }
            Process exec = Runtime.getRuntime().exec(cmd, null, new File(path));
            int r = exec.waitFor();
            BufferedReader input = new BufferedReader(new InputStreamReader(exec.getInputStream()));
            String line = "";
            while ((line = input.readLine()) != null) {
                if (line.indexOf("\0")>0) {
                    System.out.println(line.substring(0, line.indexOf("\0")));
                }else {
                    System.out.println(line);
                }
//                if (StrUtil.isNumeric(line)) {
//                    r = Integer.valueOf(line).intValue();
//                }
            }
            input.close();
            BufferedReader err = new BufferedReader(new InputStreamReader(exec.getErrorStream()));
            String errLine = "";
            while ((errLine = err.readLine()) != null) {
                System.out.println(errLine);
//                r = -1;
            }
            return r;
        } else {

            throw new CommonException("version is unavailable");
        }
    }

    private void modifyConfigFile(SoftwareVersion softwareVersion,  LicenseApplication licenseApplication) throws ParserConfigurationException, SAXException, IOException, TransformerException {
        if (softwareVersion.getBuilderType()!=null&& softwareVersion.getBuilderType().equals("5.0")){
            modifyConfig5(softwareVersion, licenseApplication);
        }else if (softwareVersion.getBuilderType()!=null&& softwareVersion.getBuilderType().equals("8.0")){
            modifyConfig8(softwareVersion, licenseApplication);
        }
    }

    private void modifyConfig5(SoftwareVersion softwareVersion, LicenseApplication licenseApplication) throws ParserConfigurationException, SAXException, IOException, TransformerException {
        String configFileName = softwareVersion.getConfigFileName();
        String path = softwareVersion.getPath();
        File configFile = new File(path + File.separator + configFileName);

        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(configFile);
        NodeList license_id = document.getElementsByTagName("license-id");
        license_id.item(0).setTextContent(licenseApplication.getId());
        NodeList license_name = document.getElementsByTagName("license-name");
        license_name.item(0).setTextContent(licenseApplication.getId());
        String customerId = licenseApplication.getCustomerId();
        CustomerInfo customerInfo = customerInfoService.queryEntity(customerId);
        NodeList company = document.getElementsByTagName("company");
        company.item(0).setTextContent(customerInfo.getName());
        NodeList product = document.getElementsByTagName("product");
        product.item(0).setTextContent(licenseApplication.getModule());
        NodeList version = document.getElementsByTagName("version");
        version.item(0).setTextContent(softwareVersion.getVersion());
        if (licenseApplication.getEndDate()!=null) {
            NodeList expiresDate = document.getElementsByTagName("expiresDate");
            expiresDate.item(0).setTextContent(new SimpleDateFormat("yyyy-MM-dd").format(licenseApplication.getEndDate()));
            NodeList expires = document.getElementsByTagName("expires");
            expires.item(0).setTextContent("true");
        }else {
            NodeList expires = document.getElementsByTagName("expires");
            expires.item(0).setTextContent("false");
        }
        if (licenseApplication.getMacAddress()!=null&& !licenseApplication.getMacAddress().isEmpty()   ) {
            NodeList localCheck = document.getElementsByTagName("localCheck");
            localCheck.item(0).setTextContent(licenseApplication.getMacAddress());
            NodeList localCheckType = document.getElementsByTagName("localCheckType");
            localCheckType.item(0).setTextContent("MAC_LOCAL_CHECK");
        }else {
            NodeList localCheckType = document.getElementsByTagName("localCheckType");
            localCheckType.item(0).setTextContent("NO_LOCAL_CHECK");
        }
        NodeList userValue = document.getElementsByTagName("userValue");
        if(userValue.getLength()>0){
            for (int i=0;i<userValue.getLength();i++){
                Node item = userValue.item(i);
                if (item instanceof Element) {
                    Element node = (Element) userValue.item(i);
                    String name = node.getAttribute("name");
                    if (name.equals("MAX_LICENSEUSER_NUMBER")){
                        node.setAttribute("value", licenseApplication.getMaxUser().toString());
                    }
                }
            }
        }

        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes"); // 美化输出
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        DOMSource source = new DOMSource(document);
        StreamResult result = new StreamResult(configFile);
        transformer.transform(source, result);
    }



    private void modifyConfig8(SoftwareVersion softwareVersion, LicenseApplication licenseApplication) throws ParserConfigurationException, SAXException, IOException, TransformerException {
        String configFileName = softwareVersion.getConfigFileName();
        String userInfoFileName = softwareVersion.getUserInfoFileName();
        String path = softwareVersion.getPath();
        File userInfoFile = new File(path + File.separator + userInfoFileName);
        File configFile = new File(path + File.separator + configFileName);
        //填写机器信息
        if (licenseApplication.getMachineInfo()!=null&& !licenseApplication.getMachineInfo().isEmpty()){
            //有获取的机器信息，最准确，直接使用
            FileOutputStream c = new FileOutputStream(userInfoFile);
            c.write(licenseApplication.getMachineInfo().getBytes(StandardCharsets.UTF_8));
            c.close();
        }else {
            //没有，手动录入mac or 不限制
            ObjectNode userNode = objectMapper.createObjectNode();
            ArrayNode macAddressList = objectMapper.createArrayNode();
            if (licenseApplication.getMacAddress() != null&&!licenseApplication.getMacAddress().isEmpty()) {
                if (licenseApplication.getMacAddress().indexOf(",")>0){
                    String[] macStrings = licenseApplication.getMacAddress().split(",");
                    Arrays.stream(macStrings).toList().forEach(macAddressList::add);
                }else
                    macAddressList.add(licenseApplication.getMacAddress());
                userNode.put("macAddressList", macAddressList);
            }
            String macString = objectMapper.writeValueAsString(userNode);
            FileOutputStream c = new FileOutputStream(userInfoFile);
            c.write(macString.getBytes(StandardCharsets.UTF_8));
            c.close();
        }
        //填写申请信息
        objectMapper.setTimeZone(TimeZone.getTimeZone("GMT+8"));
        ObjectNode configNode = objectMapper.createObjectNode();
        configNode.put("description", licenseApplication.getDescription());

        configNode.put("version", softwareVersion.getVersion());
        configNode.put("type", licenseApplication.getType());
        if (licenseApplication.getMachineInfo()!=null&& !licenseApplication.getMachineInfo().isEmpty()) {
            configNode.put("validMode", "machineinfo");
        }else {
            configNode.put("validMode", "mac");
        }
        configNode.put("maxSessionUser", licenseApplication.getMaxUser());
        if (licenseApplication.getStartDate()!=null){
            configNode.put("startDate", new SimpleDateFormat(DATE_TIME_FORMAT).format(licenseApplication.getStartDate()));
        }
        if (licenseApplication.getEndDate()!=null){
            configNode.put("endDate", new SimpleDateFormat(DATE_TIME_FORMAT).format(licenseApplication.getEndDate()));
        }
        ArrayNode moduleList = objectMapper.createArrayNode();
        String moduleStr = licenseApplication.getModule();
        if (moduleStr!=null&&!moduleStr.isEmpty()){
            String[] modules = moduleStr.split(",");
            Arrays.stream(modules).toList().forEach(moduleList::add);
        }
        configNode.put("module", moduleList);
        String configString = objectMapper.writeValueAsString(configNode);
        FileOutputStream c = new FileOutputStream(configFile);
        c.write(configString.getBytes(StandardCharsets.UTF_8));
        c.close();

    }



    private static String readTxtFile(String filePath) throws IOException {
        File userInfoFile = new File(filePath);
        FileInputStream fis = new FileInputStream(userInfoFile);
        InputStreamReader isr = new InputStreamReader(fis, "UTF-8");
        char[] buffer = new char[1024];
        String result = "";
        while (isr.read(buffer)>0){
            result += new String(buffer);
        }
        return result;
    }

}
